package com.fbs.wowo.util.eos;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fbs.wowo.exception.CreateEntityFailedException;
import com.fbs.wowo.util.common.Constants;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.routz.eos.rpctool.domain.*;
import com.routz.eos.rpctool.exception.EosException;
import com.routz.eos.rpctool.service.EosService;
import com.routz.eos.rpctool.util.GsonEosTypeAdapterFactory;
import com.routz.eos.rpctool.util.HttpUtils;
import com.routz.eos.rpctool.util.cypto.ec.EosPrivateKey;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service("EOSService")
public class EOSServiceImp implements EOSService {
    private static final String CREATOR = Constants.getEosCreator();
    private static final String NEWACCOUNT_URL = Constants.getEosUrl() + "/v1/chain/push_transaction";
    private static final String FEE = "1.0000 WOT";
    private static final String actionEvidence = "evidence";
    private static final String actionAuthright = "authright";

    /**
     * 生成公钥 私钥
     * @return Map
     */
    @Override
    public Map<String, String> EOSKey() {
        EosPrivateKey eosPrivateKey = new EosPrivateKey();
        Map<String, String> keys = new HashMap<>();
        keys.put("publicKey", eosPrivateKey.getPublicKey().toString());
        keys.put("privateKey", eosPrivateKey.toString());
        return keys;
    }

    /**
     * 创建新账户
     * @param newAccountName    用户账户名
     * @param publicKey         用户公钥
     * @param privateKey        用户私钥
     * @return  transactionId
     * @throws CreateEntityFailedException 创建失败
     */
    @Override
    public String newaccount(String newAccountName, String publicKey, String privateKey) throws CreateEntityFailedException {
        try {
            Key ownerKey = new Key();
            ownerKey.setKey(publicKey);
            ownerKey.setWeight(1L);
            ArrayList<Key> ownerKeys = new ArrayList<>();
            ownerKeys.add(ownerKey);

            RequiredAuth owner = new RequiredAuth();
            owner.setThreshold("1");
            owner.setKeys(ownerKeys);

            Key activeKey = new Key();
            activeKey.setKey(publicKey);
            activeKey.setWeight(1L);
            ArrayList<Key> activeKeys = new ArrayList<>();
            activeKeys.add(activeKey);

            RequiredAuth active = new RequiredAuth();
            active.setThreshold("1");
            active.setKeys(activeKeys);

            Map<String, Object> argsNewaccount = new HashMap<>();
            argsNewaccount.put("creator", CREATOR);
            argsNewaccount.put("name", newAccountName);
            argsNewaccount.put("owner", owner);
            argsNewaccount.put("active", active);
//          argsNewaccount.put("recovery", "eosio");
            AbiJsonToBin newaccountData = EosService.abiJsonToBin("eosio", "newaccount", argsNewaccount);

            Map<String, Object> argsBuyram = new HashMap<>();
            argsBuyram.put("payer", CREATOR);
            argsBuyram.put("receiver", newAccountName);
            argsBuyram.put("quant", FEE);
            AbiJsonToBin buyramData = EosService.abiJsonToBin("eosio", "buyram", argsBuyram);

            Map<String, Object> argsDelegatebw = new HashMap<>(5);
            argsDelegatebw.put("from", CREATOR);
            argsDelegatebw.put("receiver", newAccountName);
            argsDelegatebw.put("stake_net_quantity", FEE);
            argsDelegatebw.put("stake_cpu_quantity", FEE);
            argsDelegatebw.put("transfer", 0);
            AbiJsonToBin delegatebwData = EosService.abiJsonToBin("eosio", "delegatebw", argsDelegatebw);

            ChainInfo chainInfo = EosService.getChainInfo();

            PermissionLevel authorization = new PermissionLevel(CREATOR, "active");
            List<PermissionLevel> authorizations = new ArrayList<>();
            authorizations.add(authorization);

            Action actionNewaccount = new Action("eosio", "newaccount");
            actionNewaccount.setData(newaccountData.getBinargs());
            actionNewaccount.setAuthorization(authorizations);

            Action actionBuyram = new Action("eosio", "buyram");
            actionBuyram.setData(buyramData.getBinargs());
            actionBuyram.setAuthorization(authorizations);

            Action actionDelegatebw = new Action("eosio", "delegatebw");
            actionDelegatebw.setData(delegatebwData.getBinargs());
            actionDelegatebw.setAuthorization(authorizations);

            SignedTransaction signatures = new SignedTransaction();
            signatures.setExpiration(chainInfo.getTimeAfterHeadBlockTime(600000));
            signatures.setReferenceBlock(chainInfo.getHeadBlockId());
            signatures.addAction(actionNewaccount);
            signatures.addAction(actionBuyram);
            signatures.addAction(actionDelegatebw);
            signatures.sign(new EosPrivateKey(privateKey), chainInfo.getChainId());

            PackedTransaction transaction = new PackedTransaction(signatures);
            Gson gson = new GsonBuilder()
                    .registerTypeAdapterFactory(new GsonEosTypeAdapterFactory())
                    .excludeFieldsWithoutExposeAnnotation().create();
            String s = gson.toJson(transaction);
            System.out.println(s);

            JSONObject post = JSON.parseObject(HttpUtils.post(NEWACCOUNT_URL, s));
            String transaction_id = (String) post.get("transaction_id");
            return transaction_id;
        } catch (EosException e1) {
            throw new CreateEntityFailedException(e1.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            throw new CreateEntityFailedException("创建新账户异常");
        }
    }

    @Override
    public ChainInfo getChainInfo() throws CreateEntityFailedException {
        ChainInfo chainInfo = null;
        try {
            chainInfo = EosService.getChainInfo();
        } catch (IOException e) {
            e.printStackTrace();
            throw new CreateEntityFailedException(e.getMessage());
        }
        return chainInfo;
    }

    /**
     * 图文存证
     * @param userId        存证用户id
     * @param privateKey    存证用户私钥
     * @param albumId       专辑id
     * @param title         标题
     * @param writer        作者
     * @param ownerId       拥有者id
     * @param fingerprint   数据指纹
     * @param ipfsUrl       ipfs 网址
     * @return 返回EosTransactionResponse 包含transactionId，
     * @throws CreateEntityFailedException 发送网络请求可能失败抛异常
     */
    @Override
    public EosTransactionResponse imagetextEvidence(String userId, String privateKey, String albumId, String title, String writer, String ownerId, String fingerprint, String ipfsUrl) throws CreateEntityFailedException {
        String contract = "imagetext";
        return evidence(userId, privateKey, albumId, title, writer, ownerId, fingerprint, ipfsUrl, contract);
    }

    /**
     * 音频存证
     * @param userId        存证用户id
     * @param privateKey    存证用户私钥
     * @param albumId       专辑id
     * @param title         标题
     * @param writer        作者
     * @param ownerId       拥有者id
     * @param fingerprint   数据指纹
     * @param ipfsUrl       ipfs 网址
     * @return 返回EosTransactionResponse 包含transactionId，
     * @throws CreateEntityFailedException 发送网络请求可能失败抛异常
     */
    @Override
    public EosTransactionResponse audioEvidence(String userId, String privateKey, String albumId, String title, String writer, String ownerId, String fingerprint, String ipfsUrl) throws CreateEntityFailedException {
        String contract = "audio";
        return evidence(userId, privateKey, albumId, title, writer, ownerId, fingerprint, ipfsUrl, contract);
    }

    /**
     * 直播存证
     * @param userId        存证用户id
     * @param privateKey    存证用户私钥
     * @param albumId       专辑id
     * @param title         标题
     * @param writer        作者
     * @param ownerId       拥有者id
     * @param fingerprint   数据指纹
     * @param ipfsUrl       ipfs 网址
     * @return 返回EosTransactionResponse 包含交易Id，区块高度，chainId
     * @throws CreateEntityFailedException 发送网络请求可能失败抛异常
     */
    @Override
    public EosTransactionResponse liveEvidence(String userId, String privateKey, String albumId, String title, String writer, String ownerId, String fingerprint, String ipfsUrl) throws CreateEntityFailedException {
        String contract = "live";
        return evidence(userId, privateKey, albumId, title, writer, ownerId, fingerprint, ipfsUrl, contract);
    }

    /**
     * 图文确权
     * @param userId        存证用户id
     * @param privateKey    存证用户私钥
     * @param fingerprint   数据指纹
     * @return 返回EosTransactionResponse 包含交易Id，区块高度，chainId
     * @throws CreateEntityFailedException  发送网络请求可能失败抛异常
     */
    @Override
    public EosTransactionResponse imagetextAuthright(String userId, String privateKey, String fingerprint) throws CreateEntityFailedException {
        String contract = "imagetext";
        return authright(userId, privateKey, fingerprint, contract);
    }

    /**
     * 音频确权
     * @param userId        存证用户id
     * @param privateKey    存证用户私钥
     * @param fingerprint   数据指纹
     * @return 返回EosTransactionResponse 包含交易Id，区块高度，chainId
     * @throws CreateEntityFailedException  发送网络请求可能失败抛异常
     */
    @Override
    public EosTransactionResponse audioAuthright(String userId, String privateKey, String fingerprint) throws CreateEntityFailedException {
        String contract = "audio";
        return authright(userId, privateKey, fingerprint, contract);
    }

    /**
     * 直播确权
     * @param userId        存证用户id
     * @param privateKey    存证用户私钥
     * @param fingerprint   数据指纹
     * @return 返回EosTransactionResponse 包含交易Id，区块高度，chainId
     * @throws CreateEntityFailedException  发送网络请求可能失败抛异常
     */
    @Override
    public EosTransactionResponse liveAuthright(String userId, String privateKey, String fingerprint) throws CreateEntityFailedException {
        String contract = "live";
        return authright(userId, privateKey, fingerprint, contract);
    }

    /**
     * 执行交易
     * @param sellerId      卖家id
     * @param buyerId       买家id
     * @param privateKey    卖家私钥
     * @param fingerprint   数据指纹
     * @param type          交易作品类型：
     *                          audio: 语音
     *                          imagetext: 图文
     *                          live: 直播
     * @param quantity      交易费用 3.0000 RMB
     * @param memo          备注 transcpy花费3rmb购买imagetext用户的版权
     * @return 返回EosTransactionResponse 包含交易Id，区块高度，chainId
     * @throws CreateEntityFailedException 发送网络请求可能失败抛异常
     */
    @Override
    public EosTransactionResponse transcpyTrans(String sellerId, String buyerId, String privateKey, String fingerprint, String type, String quantity, String memo) throws CreateEntityFailedException {
        String contract = "transcpy";
        String action = "trans";

        Map<String, Object> args = new HashMap<>();
        args.put("from", sellerId);
        args.put("to", buyerId);
        args.put("hash", fingerprint);
        args.put("type", type);
        args.put("quantity", quantity);
        args.put("memo", memo);
        return pushTransaction(sellerId, privateKey, contract, action, args);
    }

    /**
     * 删除
     * @param userId        要删除的作品的版权的拥有者id
     * @param privateKey    拥有者的私钥
     * @param fingerprint   数据指纹
     * @param type          交易作品类型：
     *                          audio: 语音
     *                          imagetext: 图文
     *                          live: 直播
     * @param memo          备注
     * @return  返回EosTransactionResponse 包含交易Id，区块高度，chainId
     * @throws CreateEntityFailedException 发送网络请求可能失败抛异常
     */
    @Override
    public EosTransactionResponse transcpyDeleteitem(String userId, String privateKey, String fingerprint, String type, String memo) throws CreateEntityFailedException {
        String contract = "transcpy";
        String action = "deleteitem";

        Map<String, Object> args = new HashMap<>();
        args.put("from", userId);
        args.put("fingerprint", fingerprint);
        args.put("type", type);
        args.put("memo", memo);
        return pushTransaction(userId, privateKey, contract, action, args);
    }

    /************************************************************************************************
     *
     * 私有方法
     *
     ************************************************************************************************/

    /* 确权 */
    private EosTransactionResponse authright(String userId, String privateKey, String fingerprint, String contract) {
        Map<String, Object> args = new HashMap<>();
        args.put("user", userId);
        args.put("fingerprint", fingerprint);
        return pushTransaction(userId, privateKey, contract, actionAuthright, args);
    }

    /* 存证 */
    private static EosTransactionResponse evidence(String userId, String privateKey, String albumId, String title, String writer, String ownerId, String fingerprint, String ipfsUrl, String contract) throws CreateEntityFailedException {
        Map<String, Object> args = new HashMap<>();
        args.put("user", userId);
        args.put("Album", albumId);
        args.put("title", title);
        args.put("writer", writer);
        args.put("owner", ownerId);
        args.put("fingerprint", fingerprint);
        args.put("ipfs_url", ipfsUrl);
        return pushTransaction(userId, privateKey, contract, actionEvidence, args);
    }

    /* 交易执行*/
    private static EosTransactionResponse pushTransaction(String actor, String privateKey, String contract, String action, Map<String, Object> args) throws CreateEntityFailedException {
        String transaction = null;
        try {
            transaction = EosService.pushTransaction(actor, contract, action, privateKey, args);
        } catch (EosException e) {
            throw new CreateEntityFailedException(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            throw new CreateEntityFailedException(e.getMessage());
        }
        JSONObject tranJSON = JSON.parseObject(transaction);
        // 交易id
        String transactionId = tranJSON.getString("transaction_id");
        // 链id
        String chainId = Constants.getEosChainId();
        // 区块高度
        String blockNum = tranJSON.getJSONObject("processed").getString("block_num");
        return new EOSServiceImp().new EosTransactionResponse(transactionId, chainId, blockNum);
    }

    /************************************************************************************************
     *
     * 响应类
     *
     * transactionId    交易id
     * chainId          链id
     * blockNum         区块高度
     *
     ************************************************************************************************/
    public class EosTransactionResponse {
        private String transactionId;
        private String chainId;
        private String blockNum;

        public EosTransactionResponse(String transactionId, String chainId, String blockNum) {
            this.transactionId = transactionId;
            this.chainId = chainId;
            this.blockNum = blockNum;
        }

        public String getTransactionId() {
            return transactionId;
        }

        public void setTransactionId(String transactionId) {
            this.transactionId = transactionId;
        }

        public String getChainId() {
            return chainId;
        }

        public void setChainId(String chainId) {
            this.chainId = chainId;
        }

        public String getBlockNum() {
            return blockNum;
        }

        public void setBlockNum(String blockNum) {
            this.blockNum = blockNum;
        }
    }
}
